/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module alu(
	input	wire[9:0]			alu_op_i,
	input	wire[`RegDataBus]	operand_1_i,
	input	wire[`RegDataBus]	operand_2_i,
	input	wire				w_suffix_i,

	output	wire[`RegDataBus]	alu_res_o,
	output	wire				z_flag_o,
    output  wire                c_flag_o,
	output	wire				n_flag_o,
	output	wire				v_flag_o
	);

	wire alu_add  = alu_op_i[0];
	wire alu_sub  = alu_op_i[1];
	wire alu_and  = alu_op_i[2];
	wire alu_or   = alu_op_i[3];
	wire alu_xor  = alu_op_i[4];
	wire alu_slt  = alu_op_i[5];
	wire alu_sltu = alu_op_i[6];
	wire alu_sll  = alu_op_i[7];
	wire alu_srl  = alu_op_i[8];
	wire alu_sra  = alu_op_i[9];

	wire[`RegDataBus] add_sub_result;
	wire[`RegDataBus] and_result;
	wire[`RegDataBus] or_result;
	wire[`RegDataBus] xor_result;
	wire[`RegDataBus] slt_result;
	wire[`RegDataBus] sltu_result;
	wire[`RegDataBus] sll_result;
	wire[`RegDataBus] srl_result;
	wire[`RegDataBus] sra_result;

	assign and_result = operand_1_i & operand_2_i;
	assign or_result = operand_1_i | operand_2_i;
	assign xor_result = operand_1_i ^ operand_2_i;

	wire[`RegDataBus] add_operand_1 = operand_1_i;
	wire[`RegDataBus] add_operand_2 = (alu_sub | alu_slt | alu_sltu)
		? ~operand_2_i : operand_2_i;
	wire add_cin = (alu_sub | alu_slt | alu_sltu);
	wire[`RegDataBus] add_result;
	wire add_cout;

	assign {add_cout, add_result} = add_operand_1 + add_operand_2 + add_cin;
	assign add_sub_result = ({`XLEN{w_suffix_i == `DISABLE}} & add_result)
		| ({`XLEN{w_suffix_i == `ENABLE}} & {{32{add_result[31]}}, add_result[31:0]});

	wire sign_diver =
		((w_suffix_i == `DISABLE) & (operand_1_i[`DATA_MSB] ^ operand_2_i[`DATA_MSB]))
		| ((w_suffix_i == `ENABLE) & (operand_1_i[31] ^ operand_2_i[31]));

	assign z_flag_o = !(|alu_res_o);
	assign c_flag_o = ((alu_add | alu_sub)
		& (((w_suffix_i == `DISABLE) & add_cout)
			| ((w_suffix_i == `ENABLE) & add_result[32])));
	assign n_flag_o =
		(w_suffix_i == `DISABLE & alu_res_o[`DATA_MSB])
		| (w_suffix_i == `ENABLE & alu_res_o[31]);
	wire v_flag_64 = (operand_1_i[`DATA_MSB] ^ alu_res_o[`DATA_MSB])
		& (((operand_1_i[`DATA_MSB] & operand_2_i[`DATA_MSB]) & alu_add)
			| ((operand_1_i[`DATA_MSB] ^ operand_2_i[`DATA_MSB]) & alu_sub));
	wire v_flag_32 = (operand_1_i[31] ^ alu_res_o[31])
		& (((operand_1_i[31] & operand_2_i[31]) & alu_add)
			| ((operand_1_i[31] ^ operand_2_i[31]) & alu_sub));
	assign v_flag_o =
		(w_suffix_i == `DISABLE & v_flag_64)
		| (w_suffix_i == `ENABLE & v_flag_32);

	assign slt_result[`DATA_MSB:1] = `DATA_MSB'h0;
	assign slt_result[0] = (operand_1_i[`DATA_MSB] & ~operand_2_i[`DATA_MSB])
		| ((~sign_diver) & add_result[`DATA_MSB]);

	assign sltu_result[`DATA_MSB:1] = `DATA_MSB'h0;
	assign sltu_result[0] = ~add_cout;

	wire[31:0] sll_32 = operand_1_i[31:0] << operand_2_i[4:0];
	wire[`RegDataBus] sll_64 = operand_1_i << operand_2_i[5:0];
	assign sll_result = ({`XLEN{w_suffix_i == `DISABLE}} & sll_64)
		| ({`XLEN{w_suffix_i == `ENABLE}} & {{32{sll_32[31]}}, sll_32[31:0]});

	wire[31:0] srl_32 = operand_1_i[31:0] >> operand_2_i[4:0];
	wire[`RegDataBus] srl_64 = operand_1_i >> operand_2_i[5:0];
	assign srl_result = ({`XLEN{w_suffix_i == `DISABLE}} & srl_64)
		| ({`XLEN{w_suffix_i == `ENABLE}} & {{32{srl_32[31]}}, srl_32[31:0]});

	wire[31:0] sra_32 = ($signed(operand_1_i[31:0])) >>> operand_2_i[4:0];
	wire[`RegDataBus] sra_64 = ($signed(operand_1_i)) >>> operand_2_i[5:0];
	assign sra_result = ({`XLEN{w_suffix_i == `DISABLE}} & sra_64)
		| ({`XLEN{w_suffix_i == `ENABLE}} & {{32{sra_32[31]}}, sra_32[31:0]});

	assign alu_res_o =
		({`XLEN{alu_add | alu_sub}} & add_sub_result)
		| ({`XLEN{alu_and}} & and_result)
		| ({`XLEN{alu_or}} & or_result)
		| ({`XLEN{alu_xor}} & xor_result)
		| ({`XLEN{alu_slt}} & slt_result)
		| ({`XLEN{alu_sltu}} & sltu_result)
		| ({`XLEN{alu_sll}} & sll_result)
		| ({`XLEN{alu_srl}} & srl_result)
		| ({`XLEN{alu_sra}} & sra_result);

endmodule
